<?php

namespace paymch\models;

use Yii;
use common\models\trade\Trade;
use common\models\trade\TradeOrder;
use paymch\components\wechat\HttpDataFormatter;
use common\helpers\Utils;
use yii\helpers\ArrayHelper;

/**
 * WechatPayment is the model behind the contact form.
 * @
 */
class WechatPayment extends BasePaymentModel {

    /**
     * 返回状态码
     * @name $return_code
     * @var string(16) SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
     * @example SUCCESS
     * @static 是
     */
    public $return_code;

    /**
     * 返回信息
     * @name $return_msg
     * @var string(128) 返回信息，如非空，为错误原因
     * @example 签名失败 参数格式校验错误
     * @static 否
     */
    public $return_msg;

    /**
     * 应用Id
     * @name $appid
     * @var string(32) 微信开放平台审核通过的应用APPID
     * @example wx8888888888888888
     * @static 是
     */
    public $appid;

    /**
     * 商户号
     * @name $mch_id
     * @var string(32) 微信支付分配的商户号
     * @example 1900000109
     * @static 是
     */
    public $mch_id;

    /**
     * 设备号
     * @name $device_info
     * @var string(32) 微信支付分配的终端设备号
     * @example 13467007045764
     * @static 否
     */
    public $device_info;

    /**
     * 随机字符串
     * @name $nonce_str
     * @var string(32) 随机字符串，不长于32位
     * @example 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
     * @static 是
     */
    public $nonce_str;

    /**
     * 签名
     * @name $sign
     * @var string(32) 签名，详见签名算法
     * @example C380BEC2BFD727A4B6845133519F3AD6
     * @static 是
     */
    public $sign;

    /**
     * 业务结果
     * @name $result_code
     * @var string(16) SUCCESS/FAIL
     * @example SUCCESS
     * @static 是
     */
    public $result_code;

    /**
     * 错误代码
     * @name $err_code
     * @var string(32) 错误返回的信息描述
     * @example SYSTEMERROR
     * @static 否
     */
    public $err_code;

    /**
     * 错误代码描述
     * @name $err_code_des
     * @var string(128) 错误返回的信息描述
     * @example 系统错误
     * @static 否
     */
    public $err_code_des;

    /**
     * 用户标识
     * @name $openid
     * @var string(128) 用户在商户appid下的唯一标识
     * @example wxd930ea5d5a258f4f
     * @static 是
     */
    public $openid;

    /**
     * 是否关注公众账号
     * @name $is_subscribe
     * @var string(1) 用户是否关注公众账号，Y-关注，N-未关注，仅在公众账号类型支付有效
     * @example Y
     * @static 否
     */
    public $is_subscribe;

    /**
     * 交易类型
     * @name $trade_type
     * @var string(16) APP
     * @example APP
     * @static 是
     */
    public $trade_type;

    /**
     * 付款银行
     * @name $bank_type
     * @var string(16) 银行类型，采用字符串类型的银行标识，银行类型见银行列表
     * @example CMC
     * @static 是
     */
    public $bank_type;

    /**
     * 总金额
     * @name $total_fee
     * @var int 订单总金额，单位为分
     * @example 100
     * @static 否
     */
    public $total_fee;

    /**
     * 货币种类
     * @name $fee_type
     * @var string(8) 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型
     * @example CNY
     * @static 否
     */
    public $fee_type;

    /**
     * 现金支付金额
     * @name $cash_fee
     * @var int 现金支付金额订单现金支付金额，详见支付金额
     * @example 100
     * @static 是
     */
    public $cash_fee;

    /**
     * 现金支付货币类型
     * @name $cash_fee_type
     * @var string(16) 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型
     * @example CNY
     * @static 否
     */
    public $cash_fee_type;

    /**
     * 代金券金额
     * @name $coupon_fee
     * @var int 代金券或立减优惠金额<=订单总金额，订单总金额-代金券或立减优惠金额=现金支付金额，详见支付金额
     * @example 10
     * @static 否
     */
    public $coupon_fee;

    /**
     * 代金券使用数量
     * @name $coupon_count
     * @var int 代金券或立减优惠使用数量
     * @example 1
     * @static 否
     */
    public $coupon_count;

    /**
     * 代金券列表
     * @var array $coupons
     */
    private $coupons = [];

    /**
     * 微信支付订单号
     * @name $transaction_id
     * @var string(32) 微信支付订单号
     * @example 1217752501201407033233368018
     * @static 是
     */
    public $transaction_id;

    /**
     * 商户订单号
     * @name $out_trade_no
     * @var string(32) 商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
     * @example 1212321211201407033568112322
     * @static 是
     */
    public $out_trade_no;

    /**
     * 商家数据包
     * @name $attach
     * @var string(32) 商家数据包，原样返回
     * @example 123456
     * @static 否
     */
    public $attach;

    /**
     * 支付完成时间
     * @name $time_end
     * @var date 支付完成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
     * @example 20141030133525
     * @static 是
     */
    public $time_end;

    /**
     * 交易支付订单模型
     * @var type $_trade
     */
    private $_trade = null;

    /**
     * @inheritdoc
     *
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
     * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
     * 3、校验通知中的seller_id（或者seller_email) 是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
     * 4、验证appid是否为该商户本身。
     * 上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。
     * 
     */
    public function rules() {
        return [
            ['return_code', 'required'],
            //校验return_code
            ['return_code', 'validateReturnCode'],
            // notify_time, notify_type, notify_id, appid, charset, version, sign_type, sign, trade_no, out_trade_no are required
            [['appid', 'mch_id', 'nonce_str', 'sign', 'result_code', 'openid', 'trade_type', 'bank_type', 'total_fee', 'transaction_id', 'out_trade_no', 'time_end'], 'required'],
            //
            [['appid', 'mch_id', 'device_info', 'nonce_str', 'sign', 'err_code', 'transaction_id', 'out_trade_no'], 'string', 'max' => 32],
            [['result_code', 'trade_type', 'bank_type', 'cash_fee_type'], 'string', 'max' => 16],
            [['return_msg', 'err_code_des', 'openid', 'attach'], 'string', 'max' => 128],
            [['is_subscribe'], 'string', 'max' => 1],
            [['fee_type'], 'string', 'max' => 8],
            //
            [['time_end'], 'date', 'format' => 'yyyyMMddHHmmss'],
            //
            [['total_fee', 'cash_fee', 'coupon_fee', 'coupon_count', 'coupon_fee'], 'number'],
            //校验支付回调通知状态
            ['result_code', 'validateResultCode'],
            //校验appid,mch_id
            [['appid', 'mch_id'], 'validateConfigs'],
            //验证支付宝请求签名
            ['sign', 'validateSignature'],
            //校验商户订单号
            ['out_trade_no', 'validateOutTradeNo'],
            //校验订单支付金额
            ['total_fee', 'validateTotalFee'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels() {
        return [
            'appid' => '应用ID',
            'mch_id' => '商户号',
            'device_info' => '设备号',
            'nonce_str' => '随机字符串',
            'sign' => '签名',
            'result_code' => '业务结果',
            'err_code' => '错误代码',
            'err_code_des' => '错误代码描述',
            'openid' => '用户标识',
            'is_subscribe' => '是否关注公众账号',
            'trade_type' => '交易类型',
            'bank_type' => '付款银行',
            'total_fee' => '总金额',
            'fee_type' => '货币种类',
            'cash_fee' => '现金支付金额',
            'cash_fee_type' => '现金支付货币类型',
            'coupon_fee' => '代金券金额',
            'coupon_count' => '代金券使用数量',
            'coupon_id_$n' => '代金券ID',
            'coupon_fee_$n' => '单个代金券支付金额',
            'transaction_id' => '微信支付订单号',
            'out_trade_no' => '商户订单号',
            'attach' => '商家数据包',
            'time_end' => '支付完成时间',
        ];
    }

    /**
     * Validates the return_code.
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateReturnCode($attribute) {
        if (!$this->hasErrors()) {
            if ($this->$attribute !== 'SUCCESS') {
                $this->addError($attribute, $this->return_msg ?: '通信失败！');
            }
        }
    }

    /**
     * Validates the return_code.
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateResultCode($attribute) {
        if (!$this->hasErrors()) {
            if ($this->$attribute !== 'SUCCESS') {
                $err_desc = '[' . $this->err_code . ']' . $this->err_code_des;
                $this->addError($attribute, $err_desc);
                Yii::error($err_desc . "\n" . \yii\helpers\VarDumper::export($this->attributes), 'payment');
            }
        }
    }

    /**
     * Validates the appid or mch_id.
     * 验证appid或者mch_id是否为该商户本身
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateConfigs($attribute) {
        if (!$this->hasErrors()) {
            if ($this->$attribute !== $this->getPartnerParams($attribute)) {
                $this->addError($attribute, '非当前应用的支付商户！');
            }
        }
    }

    /**
     * Validates the signature.
     * This method serves as the inline validation for password.
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validateSignature($attribute) {
        if (!$this->hasErrors()) {
            $formatter = new HttpDataFormatter();
            $formatter->partner_key = $this->getPartnerParams('partner_key');
            $data = ArrayHelper::merge($this->attributes, $this->coupons);
            $flag = $formatter->checkSignature($data);
            if (!$flag) {
                $this->addError($attribute, '签名校验失败！');
            }
        }
    }

    /**
     * Validates the out_trade_no.
     * 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validateOutTradeNo($attribute) {
        if (!$this->hasErrors()) {
            $trade = $this->getTradeOrder();
            if (!$trade) {
                $this->addError($attribute, '商户订单号不存在！');
            }
        }
    }

    /**
     * Validates the total_amount.
     * 判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额）
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validateTotalFee($attribute) {
        if ($this->hasErrors()) {
            return;
        }
        $trade = $this->getTradeOrder();
        if (!$trade) {
            return $this->addError($attribute, '商户订单信息不存在！');
        }

        if (time() < strtotime('2020-04-02 12:00:00')) {
            $dcut = abs(intval($trade->pay * 100) - intval($this->total_fee));
            if ($dcut > 1) {
                return $this->addError($attribute, '商户订单的支付金额不一致！[' . intval($trade->pay * 100) . '-' . $this->total_fee . ']');
            }
            return;
        }
        if (function_exists('bccomp') && bccomp($trade->pay * 100, $this->total_fee) !== 0) {
            return $this->addError($attribute, 'bccomp 商户订单的支付金额不一致！[' . intval($trade->pay * 100) . '-' . $this->total_fee . ']');
        }
        $dcut = abs(intval($trade->pay * 100) - intval($this->total_fee));
        if ($dcut > 1) {
            return $this->addError($attribute, '商户订单的支付金额不一致！[' . intval($trade->pay * 100) . '-' . $this->total_fee . ']');
        }
    }

    /**
     * 查询商户信息是否存在
     * @return 
     */
    protected function getPartnerParams($key) {
        return ArrayHelper::getValue(Yii::$app->params, [
            'wechat',
            $this->appid,
            $key
        ]);
    }

    /**
     * Finds trade by [[trade_no]]
     *
     * @return Trade|null
     */
    protected function getTradeOrder() {
        if ($this->_trade === null) {
            $this->_trade = TradeOrder::findByTn($this->out_trade_no, TradeOrder::PAYMODE_WECHAT);
        }
        return $this->_trade;
    }

    public function setCouponFees($data) {
        for ($i = 0; $i < count($data); $i++) {
            $id = 'coupon_id_' . $i;
            $fee = 'coupon_fee_' . $i;
            if (isset($data[$id])) {
                $this->coupons[$id] = $data[$id];
            }
            if (isset($data[$fee])) {
                $this->coupons[$fee] = $data[$fee];
            }
        }
    }

    /**
     * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。
     * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
     * 
     * @param string $email the target email address
     * @return bool whether the email was sent
     */
    public function save(&$msg) {
        if (!$this->validate()) {
            return false;
        }

        $trade = $this->_trade;
        if (!$trade) {
            $msg = '交易订单不存在！';
            return false;
        }
        $trade->paymode = TradeOrder::PAYMODE_WECHAT;
        $trade->pay_type = $this->trade_type;
        $trade->pay_no = $this->transaction_id;
        $trade->pay_time = (string) strtotime($this->time_end);
        $trade->status = TradeOrder::STATUS_SUCCESS;

        if (empty($trade->dirtyAttributes)) {
            return $trade->trade_id;
        }

        if (!$trade->save()) {
            $msg = '交易保存时发生系统内部错误' . Utils::arrayToString($trade->firstErrors, true);
            return false;
        }

        return $trade->trade_id;
    }

}
